self.Module = {
    onRuntimeInitialized: function () {
        onWasmLoaded();
    }
};

self.importScripts("common.js");
// self.importScripts("libffmpeg.js");
self.importScripts("videodec.js");

function Decoder() {
    this.logger             = new Logger("Decoder");
    this.coreLogLevel       = 1;
    this.accurateSeek       = true;
    this.wasmLoaded         = false;
    this.tmpReqQue          = [];
    this.cacheBuffer        = null;
    this.videoCallback      = null;
    this.audioCallback      = null;
    this.requestCallback    = null;
    this.statisInfoCallback = null;
    this.rtmpStatusCallback = null; // RTMP状态回调函数
}

Decoder.prototype.initDecoder = function (url, fileSize, chunkSize, isLive) {
    var url_ptr = allocate(intArrayFromString(url), 'i8', ALLOC_NORMAL);
    var ret = Module._initDecoder(url_ptr, fileSize, isLive, this.coreLogLevel, 
                    this.requestCallback, this.statisInfoCallback, this.rtmpStatusCallback);
    _free(url_ptr);

    this.logger.logInfo("initDecoder return " + ret + ".");
    if (0 == ret) {
        this.cacheBuffer = Module._malloc(chunkSize);
    }
    var objData = {
        t: kInitDecoderRsp,
        e: ret
    };
    self.postMessage(objData);

    this.isLive = isLive;
};

Decoder.prototype.uninitDecoder = function () {
    var ret = Module._uninitDecoder();
    this.logger.logInfo("Uninit ffmpeg decoder return " + ret + ".");
    if (this.cacheBuffer != null) {
        Module._free(this.cacheBuffer);
        this.cacheBuffer = null;
    }
};

Decoder.prototype.openDecoder = function () {
    var paramCount = 8, paramSize = 4;
    var paramByteBuffer = Module._malloc(paramCount * paramSize);
    var ret = Module._openDecoder(paramByteBuffer, paramCount, this.videoCallback, this.audioCallback);
    // this.logger.logInfo("openDecoder return " + ret);

    if (ret == 0) {
        var paramIntBuff    = paramByteBuffer >> 2;
        var paramArray      = Module.HEAP32.subarray(paramIntBuff, paramIntBuff + paramCount);
        var duration        = paramArray[0];
        var videoPixFmt     = paramArray[1];
        var videoWidth      = paramArray[2];
        var videoHeight     = paramArray[3];
        var audioSampleFmt  = paramArray[4];
        var audioChannels   = paramArray[5];
        var audioSampleRate = paramArray[6];
        var keyframeCount   = paramArray[7];        // 关键帧个数，点播文件大于0时，可调用_getKeyFrames(paramByteBuffer, keyframeCount*2)获取关键帧的时间和文件位置

        var objData = {
            t: kOpenDecoderRsp,
            e: ret,
            v: {
                d: duration,
                p: videoPixFmt,
                w: videoWidth,
                h: videoHeight
            },
            a: {
                f: audioSampleFmt,
                c: audioChannels,
                r: audioSampleRate
            },
            k: keyframeCount
        };
        self.postMessage(objData);
    } else {
        var objData = {
            t: kOpenDecoderRsp,
            e: ret
        };
        self.postMessage(objData);
    }
    Module._free(paramByteBuffer);
};

Decoder.prototype.closeDecoder = function () {
    this.logger.logInfo("closeDecoder.");
    var ret = Module._closeDecoder();
    this.logger.logInfo("Close ffmpeg decoder return " + ret + ".");

    var objData = {
        t: kCloseDecoderRsp,
        e: 0
    };
    self.postMessage(objData);
};

Decoder.prototype.decode = function () {
    var ret = Module._decodeOnePacket();
    var ts = 0;
    if (ret == 0)
        ts = Module._maxTimestamp();
    self.postMessage({
        t: kDecodeRsp,
        e: ret,
        s: ts
    });
};

Decoder.prototype.pause = function() {
    var ret = Module._pauseDecode();
    self.postMessage({
        t: kPauseDecodeRsp,
    });
};

Decoder.prototype.resume = function() {
    var ret = Module._resumeDecode();
    self.postMessage({
        t: kResumeDecodeRsp,
    });
};

Decoder.prototype.sendData = function (data, offset, seqno) {
    var typedArray = new Uint8Array(data);
    Module.HEAPU8.set(typedArray, this.cacheBuffer);
    Module._sendData(this.cacheBuffer, offset, typedArray.length, seqno);
    // console.log("sendData.")
};

Decoder.prototype.seekTo = function (ms) {
    var accurateSeek = this.accurateSeek ? 1 : 0;
    var ret = Module._seekTo(ms, accurateSeek);
    var objData = {
        t: kSeekToRsp,
        r: ret,
        ms: ms
    };
    self.postMessage(objData);
};

Decoder.prototype.changeSource = function(url) {
    var url_ptr = allocate(intArrayFromString(url), 'i8', ALLOC_NORMAL);
    var ret = Module._changeSource(url_ptr);
    _free(url_ptr);

    if (ret != 0) {
        console.log("RTMP change source failed. err " + ret);
    }
};

Decoder.prototype.stopDownload = function(offset, status) {
    Module._stopDownload(offset, status);
}

Decoder.prototype.processReq = function (req) {
    // this.logger.logInfo("processReq " + req.t + ".");
    switch (req.t) {
        case kInitDecoderReq:
            this.initDecoder(req.u, req.s, req.c, req.i);
            break;
        case kUninitDecoderReq:
            this.uninitDecoder();
            break;
        case kOpenDecoderReq:
            this.openDecoder();
            break;
        case kCloseDecoderReq:
            this.closeDecoder();
            break;
        case kFeedDataReq:
            this.sendData(req.d, req.o, req.s);
            break;
        case kSeekToReq:
            this.seekTo(req.ms);
            break;
        case kChangeSourceReq:
            this.changeSource(req.u);
            break;
        case kDownloadStopped:
            this.stopDownload(req.o, req.s, req.n);
            break;
        case kDecodeReq:
            this.decode();
            break;
        case kPauseDecodeReq:
            this.pause();
            break;
        case kResumeDecodeReq:
            this.resume();
            break;
        default:
            this.logger.logError("Unsupport messsage " + req.t);
    }
};

Decoder.prototype.cacheReq = function (req) {
    if (req) {
        this.tmpReqQue.push(req);
    }
};

Decoder.prototype.onWasmLoaded = function () {
    this.logger.logInfo("Wasm loaded.");
    this.wasmLoaded = true;

    this.videoCallback = Module.addFunction(function (buff, size, timestamp) {
        var outArray = Module.HEAPU8.subarray(buff, buff + size);
        var data = new Uint8Array(outArray);
        var objData = {
            t: kVideoFrame,
            s: timestamp,
            d: data
        };
        self.postMessage(objData, [objData.d.buffer]);
    }, 'viii');

    this.audioCallback = Module.addFunction(function (buff, size, timestamp) {
        var outArray = Module.HEAPU8.subarray(buff, buff + size);
        var data = new Uint8Array(outArray);
        var objData = {
            t: kAudioFrame,
            s: timestamp,
            d: data
        };
        self.postMessage(objData, [objData.d.buffer]);
    }, 'viii');

    this.requestCallback = Module.addFunction(function (offset, availble, seqno) {
        var objData = {
            t: kRequestDataEvt,
            o: offset,
            a: availble,
            s: seqno,
        };
        self.postMessage(objData);
    }, 'vii');
    
    this.statisInfoCallback = Module.addFunction(function(recv_bytes, recv_pkts, decoded_frames, skip_frames, display_frames, decode_used_time) {
        if (!this.isLive)
            return;

        console.log(">> STATIS INFO: recv_bytes=" 
            + recv_bytes + ", recv_pkts="
            + recv_pkts + ", decoded_frames="
            + decoded_frames + ", skip_frames="
            + skip_frames + ", display_frames="
            + display_frames + ", decode_used_time="
            + decode_used_time);        
    }, 'viiiiii');

    this.rtmpStatusCallback = Module.addFunction(function(status, errcode) {
        if (status == kRtmpStatusClosed) {
            console.log("RTMP closed, err " + errcode);
        }
        else if (status == kRtmpStatusConnecting) {
            console.log("RTMP connecting ...");
        }
        else if (status == kRtmpStatusConnected) {
            console.log("RTMP connected.");
        }
        else if (status == kRtmpStatusPlaying) {
            console.log("RTMP can play now.");
        }
    }, 'vii');

    while (this.tmpReqQue.length > 0) {
        var req = this.tmpReqQue.shift();
        this.processReq(req);
    }
};

self.decoder = new Decoder;

self.onmessage = function (evt) {
    if (!self.decoder) {
        console.log("[ER] Decoder not initialized!");
        return;
    }

    var req = evt.data;
    if (!self.decoder.wasmLoaded) {
        self.decoder.cacheReq(req);
        self.decoder.logger.logInfo("Temp cache req " + req.t + ".");
        return;
    }

    self.decoder.processReq(req);
};

function onWasmLoaded() {
    if (self.decoder) {
        self.decoder.onWasmLoaded();
    } else {
        console.log("[ER] No decoder!");
    }
}
